﻿/** \file
* \$Rev: 189 $
* 
* \$Date: 2010-06-28 03:02:44 +0000 (Mon, 28 Jun 2010) $
*
* \$URL: http://chorus-twitter.googlecode.com/svn/trunk/src/twitter/User.cs $
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using chorus.utils;

namespace chorus.twitter
{
	/// <summary>
	/// A Twitter user (not necessarily the authenticating one).
	/// </summary>
	public class user : TwitterID, IKeyed<string>, gui.IDisplayRenamable
	{
		#region XML serialization

		/// <summary>The user's actual name.</summary>
		public string name { get; set; }

		/// <summary>The user's screen name.</summary>
		public string screen_name { get; set; }

		/// <summary>The user's location.</summary>
		public string location { get; set; }

		/// <summary>A description of the user.</summary>
		public string description { get; set; }

		/// <summary>A URL to the user's (small) profile image.</summary>
		public SerUri profile_image_url { get; set; }

		/// <summary>A URL to the user's homepage.</summary>
		public string url { get; set; }

		/// <summary>Whether this account is protected.</summary>
		[XmlElement("protected")]
		public SerStruct<bool> protect { get; set; }

		/// <summary>The number of people that follow the user.</summary>
		public SerStruct<uint> followers_count { get; set; }

		/// <summary>The colour of the user's background.</summary>
		public SerColour profile_background_color { get; set; }

		/// <summary>The colour of the user's text.</summary>
		public SerColour profile_text_color { get; set; }

		/// <summary>The colour of the user's links.</summary>
		public SerColour profile_link_color { get; set; }

		/// <summary>The colour of the user's sidebar background.</summary>
		public SerColour profile_sidebar_fill_color { get; set; }

		/// <summary>The colour of the user's sidebar border.</summary>
		public SerColour profile_sidebar_border_color { get; set; }

		/// <summary>The number of people that the user follows.</summary>
		public SerStruct<uint> friends_count { get; set; }

		/// <summary>The time at which this user's profile was created.</summary>
		public SerDate created_at { get; set; }

		/// <summary>The number of favourites that the user has added.</summary>
		public SerStruct<uint> favourites_count { get; set; }

		/// <summary>The user's UTC offset.</summary>
		public SerStruct<int> utc_offset { get; set; }

		/// <summary>The user's time zone.</summary>
		public string time_zone { get; set; }

		/// <summary>A URL to the user's background image.</summary>
		public SerUri profile_background_image_url { get; set; }

		/// <summary>Whether the user's background image is tiled.</summary>
		public SerStruct<bool> profile_background_tile { get; set; }

		/// <summary>Whether the user is issuing notifications to the authenticating account.</summary>
		public SerAccountDict<bool> notifications
		{
			get { return null; }
			set { value.MergeDict(notificationsdict); }
		}

		/// <summary>Whether the user has geolocation enabled.</summary>
		public SerStruct<bool> geo_enabled { get; set; }

		/// <summary>Whether the user is verified.</summary>
		public SerStruct<bool> verified { get; set; }

		/// <summary>Whether the authenticating user is following this user.</summary>
		public SerAccountDict<bool> following
		{
			get { return null; }
			set { value.MergeDict(followingdict); }
		}

		/// <summary>The number of statuses that the user has published.</summary>
		public SerStruct<uint> statuses_count { get; set; }

		/// <summary>The user's language.</summary>
		public string lang { get; set; }

		/// <summary>Whether account contributors are enabled.</summary>
		public SerStruct<bool> contributors_enabled { get; set; }

		/// <summary>The user's current status.</summary>
		public status status { get; set; }

		#endregion

		#region Fields

		/// <summary>The large profile image.</summary>
		Image profileLarge;
		/// <summary>The small profile image.</summary>
		Image profileSmall;

		/// <summary>The decimal number portion of the coordinate regex.</summary>
		const string regdec = "([+\\-]?[0-9]+\\.?[0-9]*)";
		/// <summary>The regex to find latitude and longitude in a location.</summary>
		static Regex regcoords = new Regex(regdec + " *, *" + regdec);
		/// <summary>A link to an appropriate Google Maps page for the location property.</summary>
		string maplink;
		/// <summary>The map of account IDs to notification flags.</summary>
		public readonly Dictionary<UInt64, bool> notificationsdict = new Dictionary<UInt64, bool>();
		/// <summary>The map of account IDs to following flags.</summary>
		public readonly Dictionary<UInt64, bool> followingdict = new Dictionary<UInt64, bool>();

		#endregion

		#region Properties

		/// <summary>The key to index with.</summary>
		string IKeyed<string>.Key { get { return screen_name; } }
		/// <summary>The display name.</summary>
		[XmlIgnore]
		public string DisplayName { get { return screen_name; } set { screen_name = value; } }
		/// <summary>The large profile image.</summary>
		public Image ProfileLarge { set { profileLarge = value; } }
		/// <summary>The small profile image.</summary>
		public Image ProfileSmall { set { profileSmall = value; } }
		/// <summary>The link to the user's twitter profile.</summary>
		public string ProfileUrl { get { return "https://twitter.com/" + screen_name; } }
		/// <summary>A string describing the time zone.</summary>
		public string TimeZone
		{
			get { return time_zone + " (UTC" + utc_offset.ToString() + ")"; }
		}
		/// <summary>A link to an appropriate Google Maps page for the location property.</summary>
		public string MapLink
		{
			get
			{
				if (maplink == null)
				{
					maplink = "http://maps.google.ca/maps?q=";
					Match ml = regcoords.Match(location);
					if (ml.Groups.Count == 3)
						maplink += ml.Groups[1] + "," + ml.Groups[2];
					else
						maplink += location;
				}
				return maplink;
			}
		}

		#endregion

		#region Methods

		/// <summary>Load an image asynchronously.</summary>
		/// <param name="large">Whether we want the large image.</param>
		/// <param name="notify">The response callback function.</param>
		public void LoadImageAsync(bool large, Action<Image> notify)
		{
			Image target = large ? profileLarge : profileSmall;
			if (target == null)
				new UserImageRequest(large, notify, this);
			else
				notify(target);
		}

		/// <summary>Tests a character to see if it is a valid user name character.</summary>
		/// <param name="c">The character to test.</param>
		///	<returns>Whether the character is a valid user name character.</returns>
		public static bool ValidScreennameChar(char c)
		{
			return
				(c >= 'A' && c <= 'Z') ||
				(c >= 'a' && c <= 'z') ||
				(c >= '0' && c <= '9') ||
				c == '_';
		}

		/// <summary>The query string to issue for requests pertaining to this user.</summary>
		public string Query
		{
			get
			{
				return id == 0 ?
					"screen_name=" + screen_name :
					"user_id=" + id.ToString();
			}
		}

		#endregion
	}

	/// <summary>
	/// A response object for a collection of users.
	/// </summary>
	public class users
	{
		/// <summary>The users in the response.</summary>
		public user[] user { get; set; }
	}

	/// <summary>
	/// A request object for retrieving user profile images. Includes a hack to
	/// retrieve a larger profile image than usual.
	/// </summary>
	public class UserImageRequest
	{
		/// <summary>Whether the large image is being sought.</summary>
		bool large;
		/// <summary>The response callback function.</summary>
		Action<Image> notify;
		/// <summary>The HTTP request object.</summary>
		HttpWebRequest request;
		/// <summary>The user whose image we want.</summary>
		user user;

		/// <summary>The constructor.</summary>
		/// <param name="large">Whether the large image is being sought.</param>
		/// <param name="notify">The response callback function.</param>
		/// <param name="user">The user whose image we want.</param>
		public UserImageRequest(bool large, Action<Image> notify, user user)
		{
			this.large = large;
			this.notify = notify;
			this.user = user;
			Uri uri = user.profile_image_url.val;
			if (large && uri.ToString() != "http://static.twitter.com/images/default_profile_normal.png")
			{
				string luri = uri.ToString();
				string normtag = "_normal";
				int normindex = luri.LastIndexOf(normtag);
				luri = luri.Remove(normindex, normtag.Length);
				uri = new Uri(luri);
			}
			request = (HttpWebRequest)HttpWebRequest.Create(uri);
			request.BeginGetResponse(
				new AsyncCallback(HttpAsyncResponse), null);
		}

		/// <summary>The response function to receive the image.</summary>
		/// <param name="result">The asynchronous result object.</param>
		void HttpAsyncResponse(IAsyncResult result)
		{
			HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
			Stream stream = response.GetResponseStream();
			Image image = Image.FromStream(stream);
			if (large)
				user.ProfileLarge = image;
			else
				user.ProfileSmall = image;
			notify(image);
		}
	}
}
